/**
*    Copyright (C) 2008  Chase Kernan 
*    chase.kernan@gmail.com
*
*    This program is free software: you can redistribute it and/or modify
*    it under the terms of the GNU General Public License as published by
*    the Free Software Foundation, either version 3 of the License, or
*    (at your option) any later version.
*
*    This program is distributed in the hope that it will be useful,
*    but WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*    GNU General Public License for more details.
*
*    You should have received a copy of the GNU General Public License
*    along with this program.  If not, see <http://www.gnu.org/licenses/>.
* 
**/

package com.chasekernan.hxnova.server.functions;

import com.chasekernan.hxnova.core.components.Defense;
import com.chasekernan.hxnova.core.components.StarScanner;
import com.chasekernan.hxnova.core.dataholders.FleetHolder;
import com.chasekernan.hxnova.core.dataholders.Global;
import com.chasekernan.hxnova.core.dataholders.SalvageHolder;
import com.chasekernan.hxnova.core.dataholders.StarsHolder;
import com.chasekernan.hxnova.core.designs.HullDesign;
import com.chasekernan.hxnova.core.fleets.Fleet;
import com.chasekernan.hxnova.core.fleets.Salvage;
import com.chasekernan.hxnova.core.players.PlayerTurnFile;
import com.chasekernan.hxnova.core.stars.Environment;
import com.chasekernan.hxnova.server.Server;
import com.chasekernan.hxnova.utils.Vector;
import com.chasekernan.hxnova.core.players.Player;
import com.chasekernan.hxnova.core.stars.Star;

typedef ScanPoint = {
    var rangeSquared : Int;
    var location : Vector;
}
   

/**
    Handles all scanning. 
    This automatically updates what each player knows in [Server.playerTurns].
    NOTE: Fleets/Stars that are owned by the player obviously are fully scanned
    but this is handled by the server.
**/
class Scanning {
    
    /**
        The possible percent difference between the scanned population and
        the actual population.
    **/
    public static var SCANNED_POPULATION_DIFFERENCE : Float = 0.20;
    
    private static var currentPlayer : Player;
    private static var fleetsLeft : FleetHolder;
    private static var salvageLeft : SalvageHolder;
    private static var starsLeft : StarsHolder;
    
    public static function run() {
        for (player in Global.players) {
            //just for use inside other functions
            currentPlayer = player;
            
            var normalPoints = new Array<ScanPoint>();
            var penetratingPoints = new Array<ScanPoint>();
            
            addStarPoints(penetratingPoints, normalPoints);
            addFleetPoints(penetratingPoints, normalPoints);
            
            fleetsLeft = Global.fleets.clone();
            salvageLeft = cast Global.salvage.clone();
            starsLeft = Global.stars.clone();
            
            //remove the player's own fleets and stars as they're automatically
            //"scanned" by the server
            fleetsLeft.particles.set(currentPlayer.id, new Array<Fleet>());
            
            for (i in 0...starsLeft.stars.length) {
                if (starsLeft.stars[i].owner != null && 
                        starsLeft.stars[i].owner.id == currentPlayer.id) {
                    starsLeft.stars[i] = null;
                }
            }
            
            var allFleets = new Array<Fleet>();
            var i = 0;
            
            for (point in penetratingPoints) {
                allFleets = fleetsLeft.getAllFleets();
                i = allFleets.length;
                while (i-- > 0) {
                    var fleet = allFleets[i];
                    if (fleet == null) continue;
                    
                    //not taking the sqrt makes this much faster
                    if (point.location.distanceSquaredTo(fleet.location) <= 
                            point.rangeSquared) {
                        saveFleet(fleet);
                        fleetsLeft.removeParticle(fleet);
                    }
                }
                
                i = starsLeft.stars.length;
                while (i-- > 0) {
                    var star = starsLeft.stars[i];
                    if (star == null) continue;
                    
                    if (point.location.distanceSquaredTo(star.location) <= 
                            point.rangeSquared) {
                        saveStar(star);
                        starsLeft.stars[i] = null;
                    }
                }
                
                i = salvageLeft.particles.length;
                while (i-- > 0) {
                    var salvage= salvageLeft.particles[i];
                    if (salvage == null) continue;
                    
                    if (point.location.distanceSquaredTo(salvage.location) <= 
                            point.rangeSquared) {
                        Server.playerTurns[currentPlayer.id].salvage
                                .addParticle(salvage.clone());
                        salvageLeft.removeParticle(salvage);
                    }
                }
            }
            
            //remove fleets that are orbitting stars as normal scans can't
            //detect them
            allFleets = fleetsLeft.getAllFleets();
            while (i-- > 0) {
                var fleet = allFleets[i];
                if (Global.stars.getStarAtLocation(fleet.location) != null) {
                    fleetsLeft.removeParticle(fleet);
                }
            }
            
            for (point in normalPoints) {
                //can't scan a star unless its at the scanner's location.
                var star = Global.stars.getStarAtLocation(point.location);
                if (star != null) {
                    saveStar(star);
                }
                
                //range 0 scanners cant see anything else.
                if (point.rangeSquared == 0) continue;
                
                allFleets = fleetsLeft.getAllFleets();
                i = allFleets.length;
                while (i-- > 0) {
                    var fleet = allFleets[i];
                    if (fleet == null) continue;
                    
                    //not taking the sqrt makes this much faster
                    if (point.location.distanceSquaredTo(fleet.location) <= 
                            point.rangeSquared) {
                        saveFleet(fleet);
                        fleetsLeft.removeParticle(fleet);
                    }
                }
                
                i = salvageLeft.particles.length;
                while (i-- > 0) {
                    var salvage= salvageLeft.particles[i];
                    if (salvage == null) continue;
                    
                    if (point.location.distanceSquaredTo(salvage.location) <= 
                            point.rangeSquared) {
                        Server.playerTurns[currentPlayer.id].salvage
                                .addParticle(salvage.clone());
                        salvageLeft.removeParticle(salvage);
                    }
                }
            }
            
        }
        
    }
    
    private static inline function addStarPoints(
            penetratingPoints : Array<ScanPoint>, 
            normalPoints : Array<ScanPoint>) {
        var starScanner = currentPlayer.starScanner;
        if (starScanner != null) {
        
            var pRangeSquared = starScanner.penetratingRange * 
                                starScanner.penetratingRange;
            var nRangeSquared = starScanner.range *
                                starScanner.range;
                
            if (starScanner.isPenetrating()) {
                for (star in Global.stars) {
                    if (star == null) continue;
                    //TODO: Make this more efficient
                    if (star.owner.id != currentPlayer.id) continue;
                    if (star.hasScanner == null || !star.hasScanner) continue;
                    
                    penetratingPoints.push( { 
                            rangeSquared : pRangeSquared, 
                            location : star.location.clone() } );
                    normalPoints.push( {
                            rangeSquared : nRangeSquared,
                            location : star.location.clone() } );
                } 
            } else {
                for (star in Global.stars) {
                    if (star == null) continue;
                    //TODO: Make this more efficient
                    if (star.owner.id != currentPlayer.id) continue;
                    if (star.hasScanner == null || !star.hasScanner) continue;
                        
                    normalPoints.push( {
                            rangeSquared : nRangeSquared,
                            location : star.location.clone() } );
                }        
            }
        }
    }
    
    private static inline function addFleetPoints(
            penetratingPoints : Array<ScanPoint>, 
            normalPoints : Array<ScanPoint>) {
        for (fleet in Global.fleets) {
            if (fleet == null) continue;
            if (fleet.owner.id != currentPlayer.id) continue;
            
            var pRange = fleet.getPenetratingScanningRange();
            var nRange = fleet.getNormalScanningRange();
            
            if(pRange > 0) {
                penetratingPoints.push( { 
                        rangeSquared : pRange * pRange, 
                        location : fleet.location.clone() } );
            }
            
            //if the fleet has a range 0 scanner, still include it.
            if(nRange > 0 || fleet.hasScanner()) {
                normalPoints.push( {
                        rangeSquared : nRange * nRange,
                        location : fleet.location.clone() } );
            }
        }
    }
    
    private static inline function saveFleet(fleet : Fleet) {
        var turnFile = Server.playerTurns[currentPlayer.id];
        
        //TODO: Should player names just be available at the start?
        //make sure the player's name is known
        //if(turnFile.players.getPlayerById(fleet.owner.id).name == null) {
        //    turnFile.players.getPlayerById(fleet.owner.id).name = 
        //            Global.players.getPlayerById(fleet.owner.id).name;
        //}
        
        fleet = fleet.clone();
        
        fleet.scannedMass = fleet.getMass();
        fleet.scannedWarp = if (fleet.waypoints == null || 
                                    fleet.waypoints.length == 0) 0 
                            else fleet.waypoints[0].warp;
        fleet.bearing = fleet.getBearing();
        
        //send the name of the design and the hull type
        //NOTE: HA! After spending time figuring out how to handle recording
        //designs, I figured out the original stars is actually kinda lazy
        //and only sends out the design name / hull (possibly). This means
        //if your opponent has seen your design from battle, and then you
        //delete your design and create a new design with the same name, your
        //opponent will see the same design as before when the ship is scanned
        //normally instead of the more correct unknown icon/blank hull.
        
        for (stack in fleet.ships) {
            stack.armor = null;
            stack.shields = null;
            stack.type = new HullDesign(stack.type.name, stack.type.baseHull);
            stack.type.slots = null;
        }
        
        //don't null the owner or ships
        
        fleet.fuel = null;
        fleet.colonists = null;
        fleet.minerals = null;
        fleet.waypoint0 = null;
        fleet.waypoints = null;

        turnFile.fleets.addParticle(fleet);
    }
    
    private static inline function saveStar(star : Star) {
        var starInfo = new Star();
        
        starInfo.id = star.id;
        starInfo.name = star.name;
        starInfo.location = star.location.clone();
        starInfo.owner = starInfo.owner;
        
        starInfo.environment = new Environment();
        starInfo.environment.values = star.environment.values.copy();
        
        starInfo.concentrations = star.concentrations.clone();
        starInfo.concentrations.isHomeworld = null;
        
        //TODO: If pen, then save the env even if there is a starbase.
        //TODO: If a ship with a scanner survives a battle over a star system
        //with a star base, then it should get a full scan. OR if the other
        //system is friendly to the ship, it should be scanned also.
        switch (star.starbase) {
            
            case None:
                //we can see population and defence coverage
                var possibleDiff = SCANNED_POPULATION_DIFFERENCE
                                   * star.population;
                                   
                starInfo.population = Math.round(
                        Math.random() * 2 * possibleDiff - possibleDiff) 
                        + star.population;
                        
                //TODO: How much should the defense coverage be randomly 
                //adjusted?
                starInfo.scannedDefenseCoverage = 
                    if (star.owner == null || 
                        star.owner.id == Global.players.nullPlayer.id || 
                        star.owner.defenseType == null || 
                        star.defenses == null) 0.0 
                    else star.owner.defenseType.getPopCoverage(star.defenses);
                        
                starInfo.starbase = StarStarbase.None;
                
            case Design(d):
                //just add the design name and hull
                var design = new HullDesign(d.name, d.baseHull);
                design.slots = null;
                starInfo.starbase = Design(design);
        }
        
        Server.playerTurns[currentPlayer.id].stars.stars[star.id] = starInfo;
    }
}